package com.ty.user.starter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ty.user.starter.constants.Constant;
import com.ty.user.starter.entity.Menu;
import com.ty.user.starter.entity.Permission;
import com.ty.user.starter.entity.User;
import com.ty.user.starter.entity.dto.MenuDto;
import com.ty.user.starter.entity.vo.MenuVo;
import com.ty.user.starter.enums.MenuEnum;
import com.ty.user.starter.mapper.MenuMapper;
import com.ty.user.starter.mapper.PermissionMapper;
import com.ty.user.starter.service.MenuService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 菜单表 服务实现类
 * </p>
 *
 * @author minku
 * @since 2021-10-16
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    public static final String PARENT_ID = "parent_id";

    @Resource
    private PermissionMapper permissionMapper;

    /**
     * 验证菜单名称是否重复
     *
     * @param id       id
     * @param parentId 父id
     * @param name     菜单名称
     */
    private void validName(Long id, long parentId, String name) {
        QueryWrapper<Menu> query = new QueryWrapper<>();
        query.eq(PARENT_ID, parentId);
        query.eq("name", name);
        if (null != id) {
            query.ne("id", id);
        }
        Assert.isTrue(baseMapper.selectCount(query) == 0, "菜单名称已存在");
    }

    /**
     * 验证父id以及对应的菜单类型是否正确
     *
     * @param parentId 父id
     * @param menuType 按钮类型
     */
    private void validParentId(long parentId, MenuEnum menuType) {
        if (parentId != 0L) {
            // 验证父菜单类型
            Menu parent = this.get(parentId);
            Assert.isTrue(MenuEnum.MENU == parent.getMenuType(), "按钮类型不能拥有子菜单");
        } else {
            Assert.isTrue(MenuEnum.MENU == menuType, "按钮类型不能设置为一级菜单");
        }
    }

    /**
     * 验证菜单id以及对应的菜单类型是否正确(修改时调用)
     *
     * @param id       菜单id
     * @param menuType 菜单类型
     */
    private void validMenuType(long id, MenuEnum menuType) {
        Menu menu = this.get(id);
        if (menu.getMenuType() != menuType && menuType == MenuEnum.BUTTON) {
            QueryWrapper<Menu> query = new QueryWrapper<>();
            query.eq(PARENT_ID, id);
            Assert.isTrue(baseMapper.selectCount(query) == 0, "有子菜单的情况下,该菜单不能设置为按钮类型");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(MenuDto dto) {
        Assert.isNull(dto.getId(), Constant.ID_IS_NULL);
        this.validName(null, dto.getParentId(), dto.getName());
        this.validParentId(dto.getParentId(), dto.getMenuType());

        Menu menu = new Menu();
        BeanUtils.copyProperties(dto, menu);
        baseMapper.insert(menu);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(MenuDto dto) {
        Assert.notNull(dto.getId(), Constant.ID_NOT_NULL);
        this.validName(dto.getId(), dto.getParentId(), dto.getName());
        this.validParentId(dto.getParentId(), dto.getMenuType());
        this.validMenuType(dto.getId(), dto.getMenuType());

        Menu menu = this.get(dto.getId());
        BeanUtils.copyProperties(dto, menu);
        baseMapper.updateById(menu);
    }

    /**
     * 查询菜单信息
     *
     * @param id 菜单id
     * @return 菜单信息
     */
    private Menu get(long id) {
        Menu menu = baseMapper.selectById(id);
        Assert.notNull(menu, "菜单信息不存在");
        return menu;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(long id, boolean recursion) {
        Menu menu = baseMapper.selectById(id);
        Assert.notNull(menu, "菜单信息不存在");
        // 如果只是删除当前菜单,则判断是否有子菜单
        if (!recursion) {
            QueryWrapper<Menu> query = new QueryWrapper<>();
            query.eq(PARENT_ID, id);
            Assert.isTrue(baseMapper.selectCount(query) == 0, "当前菜单包含子菜单,不能删除");
        }
        this.deleteChildById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteChildById(Long id) {
        List<Long> idList = new ArrayList<>();
        this.selectChildById(id, idList);
        idList.add(id);
        // 先删除对应的权限菜单再删除自身
        idList.parallelStream().forEach(menuId -> {
            QueryWrapper<Permission> query = new QueryWrapper<>();
            query.eq("menu_id", menuId);
            permissionMapper.delete(query);
            baseMapper.deleteById(menuId);
        });
    }

    private void selectChildById(Long id, List<Long> ids) {
        QueryWrapper<Menu> wrapper = new QueryWrapper<>();
        wrapper.eq(PARENT_ID, id);
        wrapper.select("id");
        List<Menu> childIdList = baseMapper.selectList(wrapper);
        childIdList.parallelStream().forEach(item -> {
            ids.add(item.getId());
            // 递归查询
            this.selectChildById(item.getId(), ids);
        });
    }

    @Override
    public List<MenuVo> findAll() {
        List<MenuVo> menus = baseMapper.findAll();
        if (menus.isEmpty()) {
            return menus;
        }
        return this.tree(0L, menus);
    }

    private List<MenuVo> tree(Long pid, List<MenuVo> menus) {
        List<MenuVo> treeMenu = new ArrayList<>();
        for (MenuVo menu : menus) {
            if (pid.equals(menu.getParentId())) {
                treeMenu.add(menu);
                menu.setChildren(tree(menu.getId(), menus));
            }
        }
        return treeMenu;
    }

    @Override
    public List<MenuVo> getPermissions(User user) {
        List<MenuVo> menus;
        // 如果是超管则直接返回所有权限
        if (Boolean.TRUE.equals(user.getAdmin())) {
            menus = baseMapper.findByState();
        } else {
            menus = permissionMapper.findByUserId(user.getId());
        }
        if (menus.isEmpty()) {
            return menus;
        }
        return this.tree(0L, menus);
    }
}
